/**
  Onion HTTP server library
  Copyright (C) 2010-2018 David Moreno Montero and others

  This library is free software; you can redistribute it and/or
  modify it under the terms of, at your choice:

  a. the Apache License Version 2.0.

  b. the GNU General Public License as published by the
  Free Software Foundation; either version 2.0 of the License,
  or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of both licenses, if not see
  <http://www.gnu.org/licenses/> and
  <http://www.apache.org/licenses/LICENSE-2.0>.
*/

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <onion/mime.h>
#include <onion/utils.h>

#include "../common/updateassets.h"

void print_help();
char *funcname(const char *prefix, const char *filename);
void parse_file(const char *prefix, const char *filename, FILE * outfd,
                onion_assets_file * assets);
void parse_directory(const char *prefix, const char *dirname, FILE * outfd,
                     onion_assets_file * assets);

int main(int argc, char **argv) {
  if (argc == 1)
    print_help(argv[0]);
  int i;
  char *outfile = NULL;
  char *assetfile = "assets.h";
  // First pass cancel out the options, let only the files
  for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "--help") == 0)
      print_help();
    else if (strcmp(argv[i], "-o") == 0) {
      if (i >= argc - 1) {
        fprintf(stderr, "ERROR: Need an argument for -o");
        exit(2);
      }
      outfile = argv[i + 1];
      argv[i] = NULL;           // cancel them out.
      argv[i + 1] = NULL;
      i++;
    } else if (strcmp(argv[i], "-a") == 0) {
      if (i >= argc - 1) {
        fprintf(stderr, "ERROR: Need an argument for -a");
        exit(2);
      }
      assetfile = argv[i + 1];
      argv[i] = NULL;           // cancel them out.
      argv[i + 1] = NULL;
      i++;
    }
  }

  FILE *outfd = stdout;
  if (outfile) {
    outfd = fopen(outfile, "w");
    if (!outfd) {
      perror("ERROR: Could not open output file");
      exit(2);
    }
  }
  onion_assets_file *assets = onion_assets_file_new(assetfile);

  // Some header...
  fprintf(outfd, "/** File autogenerated by opack **/\n\n");
  fprintf(outfd, "#include <onion/request.h>\n\n");
  fprintf(outfd, "#include <onion/response.h>\n\n");
  fprintf(outfd, "#include <string.h>\n\n");

  for (i = 1; i < argc; i++) {
    if (argv[i]) {
      struct stat st;
      stat(argv[i], &st);
      if (S_ISDIR(st.st_mode)) {
        parse_directory(onion_basename(argv[1]), argv[i], outfd, assets);
      } else {
        parse_file("", argv[i], outfd, assets);
      }
    }
  }

  if (outfile) {
    fclose(outfd);
  }
  onion_assets_file_free(assets);

  return 0;
}

/// Parses a filename to a function name (changes non allowed chars to _)
char *funcname(const char *prefix, const char *filename) {
  int l = 8;
  if (prefix)
    l += strlen(prefix);
  if (filename)
    l += strlen(filename);
  char *ret = malloc(l);
  strcpy(ret, "opack_");
  if (prefix && prefix[0] != '\0')
    strcat(ret, prefix);
  if (prefix && prefix[0] != '\0' && filename && filename[0] != '\0')
    strcat(ret, "_");

  if (filename && filename[0] != '\0')
    strcat(ret, filename);
  int i = 0;
  while (ret[i] != '\0') {
    if (!((ret[i] >= 'a' && ret[i] <= 'z') || (ret[i] >= 'A' && ret[i] <= 'Z')
          || (ret[i] >= '0' && ret[i] <= '9')))
      ret[i] = '_';
    i++;
  }
  return ret;
}

/**
 * @short Generates the necesary data to the output stream.
 */
void parse_file(const char *prefix, const char *filename, FILE * outfd,
                onion_assets_file * assets) {
  FILE *fd = fopen(filename, "r");
  if (!fd) {
    fprintf(stderr, "ERROR: Cant open file %s: ", filename);
    perror("");
    onion_assets_file_free(assets);
    exit(3);
  }
  char *fname = funcname(prefix, basename((char *)filename));
  char buffer[4096];
  buffer[4095] = 0;

  snprintf(buffer, sizeof(buffer) - 1,
           "onion_connection_status %s(void *_, onion_request *req, onion_response *res);",
           fname);

  onion_assets_file_update(assets, buffer);

  fprintf(stderr, "Parsing: %s to '%s'.\n", filename, buffer);
  fprintf(outfd,
          "onion_connection_status %s(void *_, onion_request *req, onion_response *res){\n  static const char data[]={\n",
          fname);
  int r, i, l = 0;
  while ((r = fread(buffer, 1, sizeof(buffer) - 1, fd)) != 0) {
    for (i = 0; i < r; i++) {
      fprintf(outfd, "0x%02X, ", buffer[i] & 0x0FF);
      if ((i % 16) == 15) {
        fprintf(outfd, "\n");
      }
    }
    l += r;
  }
  fprintf(outfd, "};\n");

  const char *mime_type = onion_mime_get(filename);
  fprintf(outfd, "  onion_response_set_length(res, %d);\n", l);
  fprintf(outfd,
          "  onion_response_set_header(res, \"Content-Type\", \"%s\");\n",
          mime_type);
  fprintf(outfd,
          "  return onion_response_write(res, data, sizeof(data));\n}\n\n");

  fprintf(outfd, "const unsigned int %s_length = %d;\n\n", fname, l);

  fclose(fd);
  free(fname);
}

/**
 * @short Bulk converts all files at dirname, excepting *~, and the creates a handler for such directory.
 */
void parse_directory(const char *prefix, const char *dirname, FILE * outfd,
                     onion_assets_file * assets) {
  DIR *dir = opendir(dirname);
  if (!dir) {
    fprintf(stderr, "ERROR: Could not open directory %s, check permissions.",
            dirname);
    exit(4);
  }
  // First create other files/dirs
  struct dirent *de;
  char fullname[1024];
  while ((de = readdir(dir))) {
    if (de->d_name[0] == '.' || de->d_name[strlen(de->d_name) - 1] == '~')
      continue;
    snprintf(fullname, sizeof(fullname), "%s/%s", dirname, de->d_name);
    if (de->d_type == DT_DIR) {
      char prefix2[256];
      snprintf(prefix2, sizeof(prefix2), "%s/%s", prefix, de->d_name);
      parse_directory(prefix2, fullname, outfd, assets);
    } else
      parse_file(prefix, fullname, outfd, assets);
  }
  closedir(dir);
  // Now create current
  char *fname = funcname(prefix, NULL);
  snprintf(fullname, sizeof(fullname),
           "onion_connection_status %s(void *_, onion_request *req, onion_response *res);",
           fname);
  onion_assets_file_update(assets, fullname);
  fprintf(stderr, "Parsing directory: %s to '%s'.\n", dirname, fullname);
  fprintf(outfd,
          "onion_connection_status %s(void *_, onion_request *req, onion_response *res){\n",
          fname);
  fprintf(outfd, "  const char *path=onion_request_get_path(req);\n\n");

  dir = opendir(dirname);
  while ((de = readdir(dir))) {
    if (de->d_name[0] == '.' || de->d_name[strlen(de->d_name) - 1] == '~')
      continue;
    char *fname = funcname(prefix, de->d_name);
    if (de->d_type == DT_DIR) {
      int l = strlen(de->d_name);
      fprintf(outfd, "  if (strncmp(\"%s/\", path, %d)==0){\n", de->d_name,
              l + 1);
      fprintf(outfd, "    onion_request_advance_path(req, %d);\n", l + 1);
    } else
      fprintf(outfd, "  if (strcmp(\"%s\", path)==0){\n", de->d_name);
    fprintf(outfd, "    return %s(_, req, res);\n", fname);
    fprintf(outfd, "  }\n");
    free(fname);
  }
  closedir(dir);

  fprintf(outfd, "  return OCS_NOT_PROCESSED;\n");
  fprintf(outfd, "}\n\n");
  free(fname);
}

/// Shows the help
void print_help(const char *name) {
  fprintf(stderr,
          "%s -- Packs a given file or files into a C function that will print it out\n\n",
          name);
  fprintf(stderr, "Usage: %s <file1> <file2> -o <outfile.c>\n", name);
  fprintf(stderr, "       %s <dir1> <dir2> <file3> -o <outfile.c>\n\n", name);
  fprintf(stderr, "Options:\n");
  fprintf(stderr, "       --help            Shows this help\n");
  fprintf(stderr, "       -o <filename.c>   Output filename\n");
  fprintf(stderr,
          "       -a <filename.h>   Asset header file. By default assets.h\n\n");
  fprintf(stderr,
          "It later creates a series of functions, with the name of the file or directory, and with the following signature.\n");
  fprintf(stderr,
          "   int opack_[file_name_and_extension](void *_, onion_request *request, onion_response *response);\n\n");
  fprintf(stderr,
          "An asset header file is created/updated with the opack needed handlers.");
  fprintf(stderr,
          "With this signature handlers are very easily used from onion.\n\n");
  fprintf(stderr,
          "If its a directory, access is as expected using the path, but only last element: static/jquery.min.js, for example if you pack static with jquery.min.js at src/static/. It is recursive.\n");
  fprintf(stderr,
          "In directory mode, files ending with ~ and starting with . are ignored.\n");
  exit(1);
}
